﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.Serialization;

using SharpGL.Text;
using SharpGL.Media3D;
using SharpGL.SceneGraph.Helpers;

namespace SharpGL.SceneGraph.Primitives.Text
{
    /// <summary>
    /// A 3D text object text in specified position.
    /// </summary>
    [DataContract(IsReference = true)]
    public class Text3DObject : Text2DObject, IHasOpenGLContext
    {
        #region Private Fields

        /// <summary>
        /// Current OpenGL context
        /// </summary>
        private OpenGL currentOpenGLContext = null;

        /// <summary>
        /// The depth text alignment.
        /// </summary>
        private DepthAlignment depthAlignment = DepthAlignment.Far;

        /// <summary>
        /// The deviation used for create 3D text.
        /// </summary>
        private float deviation = 0.0f;

        /// <summary>
        /// The 3D text extrusion.
        /// </summary>
        private float extrusion = 0.1f;

        /// <summary>
        /// The 3D text poligon draw style.
        /// </summary>
        private FontOutlineFormat fontOutlineFormat = FontOutlineFormat.Polygons;

        /// <summary>
        /// The 3D text direction.
        /// </summary>
        private Vector3D direction = new Vector3D(1.0f, 0.0f, 0.0f);

        /// <summary>
        /// The angle (in degrees) of 3D text rotation around direction point.
        /// </summary>
        private float angle = 0.0f;

        #endregion Private Fields

        #region Protected Methods

        protected virtual void OnCreateInContext(OpenGL gl)
        {
            UpdateBoundingVolume();
        }

        protected virtual void OnDestroyInContext(OpenGL gl)
        {
            BoundingVolume.SetEmpty();
        }

        /// <summary>
        /// Perform all rendering operation to the provided instance of OpenGL.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        /// <param name="renderMode">The render mode.</param>
        protected override void InternalRenderText(OpenGL gl, RenderMode renderMode)
        {
            if (!OpenGL.Equals(CurrentOpenGLContext, gl))
                throw new Exception("Cannot render the 3D text object '" +
                                    Name +
                                    "' in context '" +
                                    (OpenGL.IsValidContext(gl) ? gl.Renderer : "Null") +
                                    "' - it is created in '" +
                                    (OpenGL.IsValidContext(CurrentOpenGLContext) ? CurrentOpenGLContext.Renderer : "Null") +
                                    "' OpenGL context.");

            Point3D alignTranslateVector = new Point3D(0.0f, 0.0f, 0.0f);
            Rect3D textRect = gl.GetTextSize(Font, deviation, extrusion, fontOutlineFormat, Text);

            if (OpenGL.IsValidContext(gl))
            {
                switch (HorizontalAlignment)
                {
                    case HorizontalAlignment.Left:
                        alignTranslateVector.Push(-textRect.X, 0.0f, 0.0f);
                        break;
                    case HorizontalAlignment.Center:
                        alignTranslateVector.Push(-textRect.X - textRect.SizeX / 2.0f, 0.0f, 0.0f);
                        break;
                    case HorizontalAlignment.Right:
                        alignTranslateVector.Push(-textRect.X - textRect.SizeX, 0.0f, 0.0f);
                        break;
                    default:
                        break;
                }

                switch (VerticalAlignment)
                {
                    case VerticalAlignment.Top:
                        alignTranslateVector.Push(0.0f, -textRect.Y - textRect.SizeY, 0.0f);
                        break;
                    case VerticalAlignment.Center:
                        alignTranslateVector.Push(0.0f, -textRect.Y - textRect.SizeY / 2.0f, 0.0f);
                        break;
                    case VerticalAlignment.Bottom:
                        alignTranslateVector.Push(0.0f, -textRect.Y, 0.0f);
                        break;
                    default:
                        break;
                }

                switch (DepthAlignment)
                {
                    case DepthAlignment.Near:
                        alignTranslateVector.Push(0.0f, 0.0f, -textRect.Z - textRect.SizeZ);
                        break;
                    case DepthAlignment.Center:
                        alignTranslateVector.Push(0.0f, 0.0f, -textRect.Z - textRect.SizeZ / 2.0f);
                        break;
                    case DepthAlignment.Far:
                        alignTranslateVector.Push(0.0f, 0.0f, -textRect.Z);
                        break;
                    default:
                        break;
                }

                Vector3D baseVector = new Vector3D(1.0f, 0.0f, 0.0f);
                Vector3D rotateVector = Vector3D.CrossProduct(baseVector, Direction);
                double rotateAngle = Vector3D.AngleBetween(baseVector, Direction);

                gl.PushMatrix();

                gl.Translate(Position.X, Position.Y, Position.Z);
                gl.Rotate(rotateAngle, rotateVector.X, rotateVector.Y, rotateVector.Z);
                gl.Rotate(Angle, 1.0, 0.0, 0.0);
                gl.Scale(Font.Size, Font.Size, 1.0f);
                gl.Translate(alignTranslateVector.X, alignTranslateVector.Y, alignTranslateVector.Z);
                gl.DrawText3D(Font, Deviation, Extrusion, FontOutlineFormat, Text);

                gl.PopMatrix();
            }
        }

        protected override void UpdateBoundingVolume()
        {
            if (OpenGL.IsValidContext(CurrentOpenGLContext))
            {
                Point3D alignTranslateVector = new Point3D(0.0f, 0.0f, 0.0f);
                Rect3D textRect = CurrentOpenGLContext.GetTextSize(Font, deviation, extrusion, fontOutlineFormat, Text);

                switch (HorizontalAlignment)
                {
                    case HorizontalAlignment.Left:
                        alignTranslateVector.Push(-textRect.X, 0.0f, 0.0f);
                        break;
                    case HorizontalAlignment.Center:
                        alignTranslateVector.Push(-textRect.X - textRect.SizeX / 2.0f, 0.0f, 0.0f);
                        break;
                    case HorizontalAlignment.Right:
                        alignTranslateVector.Push(-textRect.X - textRect.SizeX, 0.0f, 0.0f);
                        break;
                    default:
                        break;
                }

                switch (VerticalAlignment)
                {
                    case VerticalAlignment.Top:
                        alignTranslateVector.Push(0.0f, -textRect.Y - textRect.SizeY, 0.0f);
                        break;
                    case VerticalAlignment.Center:
                        alignTranslateVector.Push(0.0f, -textRect.Y - textRect.SizeY / 2.0f, 0.0f);
                        break;
                    case VerticalAlignment.Bottom:
                        alignTranslateVector.Push(0.0f, -textRect.Y, 0.0f);
                        break;
                    default:
                        break;
                }

                switch (DepthAlignment)
                {
                    case DepthAlignment.Near:
                        alignTranslateVector.Push(0.0f, 0.0f, -textRect.Z - textRect.SizeZ);
                        break;
                    case DepthAlignment.Center:
                        alignTranslateVector.Push(0.0f, 0.0f, -textRect.Z - textRect.SizeZ / 2.0f);
                        break;
                    case DepthAlignment.Far:
                        alignTranslateVector.Push(0.0f, 0.0f, -textRect.Z);
                        break;
                    default:
                        break;
                }

                Vector3D baseVector = new Vector3D(1.0f, 0.0f, 0.0f);
                Vector3D rotateVector = Vector3D.CrossProduct(baseVector, Direction);
                double rotateAngle = Vector3D.AngleBetween(baseVector, Direction);

                BoundingVolume.FromVertices(new Point3D[] { new Point3D(textRect.X, textRect.Y, textRect.Z),
                                            new Point3D(textRect.X + textRect.SizeX, textRect.Y + textRect.SizeY, textRect.Z + textRect.SizeZ) });
                BoundingVolume.Transform(
                                TransformationHelper.TranslateMatrix(Position.X, Position.Y, Position.Z) *
                                TransformationHelper.RotateMatrix(rotateAngle, rotateVector.X, rotateVector.Y, rotateVector.Z) *
                                TransformationHelper.RotateXMatrix(Angle) *
                                TransformationHelper.ScaleMatrix(Font.Size, Font.Size, 1.0f) *
                                TransformationHelper.TranslateMatrix(alignTranslateVector.X, alignTranslateVector.Y, alignTranslateVector.Z));
            }
            else
            {
                BoundingVolume.SetEmpty();
            }
        }

        #endregion Protected Methods

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Text3DObject"/> class.
        /// </summary>
        public Text3DObject()
		{
			Name = "Text 3D object";
		}

        #endregion Constructors

        #region Public Methods

        /// <summary>
        /// Create in the context of the supplied OpenGL instance.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        public virtual void CreateInContext(OpenGL gl)
        {
            if (!OpenGL.Equals(gl, CurrentOpenGLContext))
            {
                if (OpenGL.IsValidContext(CurrentOpenGLContext))
                    throw new Exception("Cannot create '" +
                                        Name +
                                        "' in context '" +
                                        (OpenGL.IsValidContext(gl) ? gl.Renderer : "Null") +
                                        "' - it is already created in '" +
                                        (OpenGL.IsValidContext(CurrentOpenGLContext) ? CurrentOpenGLContext.Renderer : "Null") +
                                        "' OpenGL context.");


                if (OpenGL.IsValidContext(gl))
                {
                    CurrentOpenGLContext = gl;

                    // Call on create in OpenGL context function.
                    OnCreateInContext(gl);

                    NotifyContextChanged(this, new ContextChangeEventArgs(ContextChangeAction.CreateInContext));
                }
            }
        }

        /// <summary>
        /// Destroy in the current OpenGL instance.
        /// </summary>
        public virtual void DestroyInContext()
        {
            if (OpenGL.IsValidContext(CurrentOpenGLContext))
            {
                NotifyContextChanged(this, new ContextChangeEventArgs(ContextChangeAction.DestroyInContext));

                // Call on destroy in OpenGL context function.
                OnDestroyInContext(CurrentOpenGLContext);

                CurrentOpenGLContext = null;
            }
        }

        #endregion Public Methods

        #region Public Propetries

        /// <summary>
        /// Current OpenGL context
        /// </summary>
        [Browsable(false)]
        public OpenGL CurrentOpenGLContext
        {
            get { return currentOpenGLContext; }
            protected set { currentOpenGLContext = value; }
        }

        /// <summary>
        /// Gets or sets the depth text alignment.
        /// </summary>
        /// <value>
        /// The depth text aligment.
        /// </value>
        [Description("The depth alignment."), Category("Text object")]
        public DepthAlignment DepthAlignment
        {
            get { return depthAlignment; }
            set
            {
                if (depthAlignment != value)
                {
                    depthAlignment = value;

                    NotifyPropertyChanged("DepthAlignment");
                }
            }
        }

        /// <summary>
        /// Gets or sets the deviation used for create 3D text.
        /// </summary>
        /// <value>
        /// The deviation used for create 3D text.
        /// </value>
        [Description("The deviation."), Category("Text object")]
        public float Deviation
        {
            get { return deviation; }
            set
            {
                if (deviation != value)
                {
                    deviation = value;

                    NotifyPropertyChanged("Deviation");
                }
            }
        }

        /// <summary>
        /// Gets or sets the 3D text extrusion.
        /// </summary>
        /// <value>
        /// the 3D text extrusion.
        /// </value>
        [Description("The extrusion."), Category("Text object")]
        public float Extrusion
        {
            get { return extrusion; }
            set
            {
                if (extrusion != value)
                {
                    extrusion = value;

                    NotifyPropertyChanged("Extrusion");
                }
            }
        }

        /// <summary>
        /// Gets or sets the 3D text poligon draw style.
        /// </summary>
        /// <value>
        /// The 3D text poligon draw style.
        /// </value>
        [Description("3D text poligon draw style."), Category("Text object")]
        public FontOutlineFormat FontOutlineFormat
        {
            get { return fontOutlineFormat; }
            set
            {
                if (fontOutlineFormat != value)
                {
                    fontOutlineFormat = value;

                    NotifyPropertyChanged("FontOutlineFormat");
                }
            }
        }

        /// <summary>
        /// Gets or sets the 3D text direction.
        /// </summary>
        /// <value>
        /// The 3D text direction.
        /// </value>
        [Description("3D text direction."), Category("Text object")]
        public Vector3D Direction
        {
            get { return direction; }
            set
            {
                if (direction != value)
                {
                    direction = value;

                    NotifyPropertyChanged("Direction");
                }
            }
        }

        /// <summary>
        /// Gets or sets the angle (in degrees) of 3D text rotation around direction point.
        /// </summary>
        /// <value>
        /// The angle (in degrees) of 3D text rotation around direction point.
        /// </value>
        [Description("The angle (in degrees) of 3D text rotation around direction point."), Category("Text object")]
        public float Angle
        {
            get { return angle; }
            set
            {
                if (angle != value)
                {
                    angle = value;

                    NotifyPropertyChanged("Angle");
                }
            }
        }

        #endregion Public Propetries
    }
}
